(async function () { /** * 1.0 与 2.0脚本的主要区别如下 * @param {shopId} - 获取都在init()中,但是操作略有不同 * @function (renderButton) - 1.0是insertAdjacentHTML, 2.0由于是挂在到liquid,主要采取替换innerHTML * * */ // warning : .shopify-payment-button 字符串被用于替换做兼容 /** * 脚本是否运行过,运行过则退出 */ // 脚本有三种,没更新的写入脚本,更新的写入脚本和扩展脚本 // 更新的写入脚本和扩展脚本同时出现时,更新的写入脚本先执行,扩展脚本会在这退出 if (typeof bis_shopify_block_status !== "undefined") { if (bis_shopify_block_status === 1) { return; } else if (bis_shopify_block_status === 2) { bis_shopify_block_status = 1; } } const scripts = document.querySelectorAll( 'script[src*="/assets/product_restore_email.js"]' ); // 没更新的写入脚本并没有这俩个判断退出,默认执行,所以扩展脚本识别到有没更新的写入脚本得退出 if (scripts.length === 2 && scripts[0].src.indexOf("extensions") === -1) { return; } // 防抖 function debounce(delay = 500, callback) { let timer; return function () { clearTimeout(timer); timer = null; timer = setTimeout(() => { callback(); }, delay); }; } // Mark 变量声明 let isProPage = false; let isCollPage = false; let productId = 0; let insertEls = []; let collVariants = []; let observer = null; let oldCollElsCount = 0; let proPosition = []; let locale; let customStyle = ""; let customPosition = null; let customFeature = null; let collectionAccess = false; const themeId = Shopify.theme.id; let executeDelay = 0; let v = { executeDelay: 0, variantListenerElement: "" }; let u = { insert_button_info: {}, custom_style_info: {} }; let updateStatus = { is_button_settings_updating: 1, is_popup_settings_updating: 1, is_integrations_updating: 1, is_variants_updating: 1, button_status: 1, lang_status: 1, }; // 获取所有数据的更新情况,是否要请求接口 let observerCallback = null; let getDataCount = 0; // 获取数据成功的次数,相当于请求成功次数,分别是按钮样式、弹窗样式、集成数据、3次成功后开始嵌入按钮和弹窗 const productPageType = 1; const collectionPageType = 2; const pageType = ShopifyAnalytics && ShopifyAnalytics.meta && ShopifyAnalytics.meta.page && ShopifyAnalytics.meta.page.pageType; let insertType = "afterend"; let emailStyle = null; const isMobile = /(iPhone|iPad|iPod|iOS|Android|SymbianOS|Windows Phone|webOS|BlackBerry)/i.test( navigator.userAgent ); if (typeof ShopifyAnalytics === "undefined") { await new Promise((res, rej) => { setTimeout(() => { res(); }, 1500); }); } if (window.ShopifyAnalytics && ShopifyAnalytics.meta.product) { productId = ShopifyAnalytics.meta.product.id; } else if (Shopify.designMode) { if (!q("#em_product_id")) { await new Promise((res, rej) => { setTimeout(() => { res(); }, 1000); }); } productId = JSON.parse(q("#em_product_id").textContent); } let shopId = 0; if (qa("#shopify-features").length !== 0) { shopId = JSON.parse(qa("#shopify-features")[0].outerText).shopId; } else if ( window.ShopifyAnalytics && ShopifyAnalytics.lib && ShopifyAnalytics.lib.config ) { shopId = ShopifyAnalytics.lib.config.Trekkie.defaultAttributes.shopId; } else { shopId = JSON.parse(q("#bis_shop_id").textContent); } // userNeed() locale = Shopify.locale; // Mark 相关请求 let requests = { getProsVariantsData(p) { const url = baseUrl + "api/v1/customer/getVariantBtStatus"; let params = {}; Object.assign(params, p); return request(url, params); }, getCollBtnStyle() { const url = baseUrl + "api/v1/getCollectionButtonStyle"; let params = { shop_language: locale, }; return request(url, params); }, /** * 获取缺货变体数据 * @param {{product_ids?:number[];product_handles?:string[];page_type:number}} extraParams - 页面类型 */ variantsData(extraParams = {}) { const url = bisBaseUrl + "api/v1/product/get_customer_variants_inventory_status"; let params = { ...extraParams, }; return request(url, params, "GET"); }, /** * 获取按钮设置 * @param {number} page_type - 页面类型 */ buttonSettings(page_type) { const url = bisBaseUrl + "api/v1/product/get_customer_button_settings"; let params = { lang: locale, page_type, }; return request(url, params, "GET"); }, /** * 获取弹窗设置 */ popupSettings() { const url = bisBaseUrl + "api/v1/product/get_customer_popup_settings"; let params = { lang: locale, }; return request(url, params, "GET"); }, integrationStatus() { const url = bisBaseUrl + "api/v1/integration/get_customer_integrations"; return request(url, {}, "GET"); }, }; // 店铺域名 const domain = Shopify.shop; /* * dev下debug为true, baseUrl为测试服。 * prod下debug为false, baseUrl为正式服 */ const baseUrl = bisApiBaseUrl; const bisBaseUrl = TRUSTOO_BIS_BASE_URL; // 登录用户的信息 let customerInfo = {}; // 能够展示按钮的变体数组 let showVariants = []; // 按钮以及弹窗的参数 const btnAndPopupData = initBtnAndPopupData(); const { buttonStyleUrl, collButtonStyleUrl, popupStyleUrl, integrationUrl, floatBtnPosition, buttonData, generalData, formAction, } = btnAndPopupData; let { iti, popupData, inlineBtnHeight, inlineBtnWidth, btnRadius, btnFontSize, btnFontWeight, insertEl, selectedType, selBtnStatus, btnStyleSwitch, popupStyleSwitch, inteStatus, } = btnAndPopupData; const elements = initElement(); let { inlineBtnElement, floatBtnElement, emailFrameElement, inlineEmailDiv, floatEmailDiv, invalidTip, successFrame, variantSelector, closeBox, submitBtn, emailInput, nameInput, smsInput, mailingCheckbox, soldOutBtn, exactForm, } = elements; const initRes = await init(); if (typeof initRes === "string") { console.log("Init info: ", initRes); //return; // 如果获得的返回值不是对象而是string,说明在中间的某一个if判断出问题了,应当直接return } else { console.log("App is on!"); } const { MAX_SEARCH_TIMES } = initRes; let { times } = initRes; const { trueForms } = elements; const productInfo = initProductInfo(); let { currentVariant, available, selectVariantId, productTitle, currentVariantOption, addOptionsStatus, initUrl, listenVariantFlag, } = productInfo; let { variantData, unVariantOptions } = productInfo; const payment_button_class = ".shopify-payment-button"; execute(); importStyles(); // 引入邮件的css // =====================================逻辑就到这里结束==================================== // =====================================逻辑就到这里结束==================================== // =====================================逻辑就到这里结束==================================== // Mark 初始化 async function init() { /* * 初始化一些店铺的信息,判断是否要继续往下执行脚本 * shopId - 店铺id,长的那个,不是user表里短的那个 * ENV - dev下debug为true, baseUrl为测试服。 * prod下debug为false, baseUrl为正式服 */ // const ENV = 'prod'; // const baseUrl = ENV.indexOf('dev') === -1 // ? 'https://emailnoticeapi.sealapps.com/' // : 'https://emailnoticeapitest.sealapps.com/'; // 从浏览器的window对象中获取ShopifyAnalytics对象 const { ShopifyAnalytics } = window; let proParams = {}; let prosEle = []; let prosEleHandle = []; isProPage = pageType === "product" || q("#sealapps-bis-widget"); isCollPage = pageType === "collection" || collectionAccess; v.isProPage = isProPage; v.isCollPage = isCollPage; if (!isCollPage && !isProPage) { return "not collectionPage or productPage"; } getUserNeedData(); if (u.custom_style_info.css_style_code) { customStyle += u.custom_style_info.css_style_code; } addCustomModify(); // 运行前执行用户需求自定义函数 if (typeof bisBeforeExecute !== "undefined") { bisBeforeExecute(v); } const delay = v.executeDelay || executeDelay; if (delay !== 0) { await new Promise((res, rej) => { setTimeout(() => { res(); }, delay); }); } setCollDeb(); const statusParams = {}; if (isProPage) { statusParams.product_ids = [productId]; } const statusRes = await request( bisBaseUrl + "api/v1/shop/get_data_update_status", statusParams, "GET" ); if (statusRes.code === 0) { updateStatus = statusRes.data; } else { updateStatus = { is_button_settings_updating: 1, is_popup_settings_updating: 1, is_integrations_updating: 1, is_variants_updating: 1, button_status: 1, lang_status: 1, }; } if (isProPage) { if (productId) { proParams.product_ids = [productId]; proParams.page_type = 1; } } else if (isCollPage) { prosEle = await getProsEle(); if (prosEle !== null) { oldCollElsCount = prosEle.length; prosEleHandle = getProductsANodes(prosEle); prosEle = prosEleHandle.map((it) => it.element); proParams.product_handles = prosEleHandle.map((it) => it.handle); proParams.page_type = 2; } else { return "No embed location found"; } } await getButtonStyle(proParams.page_type); // if (ShopifyAnalytics && ShopifyAnalytics.meta && ShopifyAnalytics.meta.page && ShopifyAnalytics.meta.page.pageType !== 'product') { // return 'Not in product page'; // } else if (location.href.indexOf('product') === -1) { // return 'Not in product page'; // } // 获取shopId // const shopId = ShopifyAnalytics.lib // ? JSON.parse(qa('#shopify-features')[0].outerText).shopId // : ShopifyAnalytics.lib.config.Trekkie.defaultAttributes.shopId; changeStatus({ baseUrl }); // 获取需要显示按钮的产品数据 // await getProductStatus(); if ( buttonData.is_active_float_btn === 1 || buttonData.is_active_inline_btn === 1 ) { if (Object.keys(proParams).length !== 0) { if (updateStatus.is_variants_updating !== 1 && !isCollPage) { if (typeof bis_sold_out_variants !== "undefined") { showVariants = bis_sold_out_variants[0].variants.map((variant) => { return variant.variant_id; }); } else { showVariants = [ { variants: [], }, ]; } } else { const res = await requests.variantsData(proParams); // if (shopId == 48441458840 && proParams.page_type === 1) { // } else if (res.btStatus !== 1) { // return "collectionPage btn disable"; // } const { code, data } = res; const products = data.products; if (isProPage) { if (code === 0 && products) { //由于是产品页,所以产品肯定只有一个 showVariants = products[0].variants.map((variant) => { return variant.variant_id; }); } } else if (isCollPage) { products.forEach((i, inx) => { const target = prosEleHandle.find((it) => it.handle === i.handle); if (target && i.variants.length !== 0) { insertEls.push(target.element); proPosition.push(inx); collVariants.push({ proId: i.product_id, productName: i.title, variants: i.variants, }); } // if (i.productStatus === 1) { // insertEls.push(prosEle[inx]); // proPosition.push(inx); // collVariants.push({ // proId: i.product_id, // productName: i.title, // variants: i.variants, // }); // } }); } } } } else { return "Button is disabled"; } const hasUnavailableV = showVariants.length || collVariants.length; if (hasUnavailableV == 0) { if (isCollPage) { // 这页产品列表没有,其他排序可能有,要设立监听 checkVariantChange(); } return "All btn of variants are hidden."; } const nodes = qa(`#product-restore-email-float,.product-restore-email`); if (nodes.length !== 0) { if (!isCollPage) { nodes.forEach((i) => i.remove()); } // return 'Already enabled'; } else { const div = ` `; document.body.insertAdjacentHTML("beforeend", div); } return { baseUrl, MAX_SEARCH_TIMES: 50, times: 0, }; } // Mark 执行 async function execute() { if (isProPage) { // // 获取需要显示按钮的产品数据 // await getProductStatus(baseUrl); if (showVariants.length === 0) { return; } if (insertEl) { handleBasicData(); getAllData(); } else { searchParentEl().then((res) => { const { code } = res; if (code === 501) { // 没有找到元素,打印出来,方便人员调试 console.log("Search Node Failed"); handleSearchNodeFailed().then((searchRes) => { if (searchRes.code === 0) { handleBasicData(); getAllData(); } }); return; } if (code === 0) { // 找到了插入的元素,继续往下执行 handleBasicData(); getAllData(); } }); } } else if (isCollPage) { if (buttonData.is_active_inline_btn === 1) { getAllData(); } } } //Mark 获取集合页产品列表的嵌入元素(价格) async function getProsEle() { const themeStoreId = Shopify.theme.theme_store_id; let selector = ""; if ( buttonData.customize_inline_btn_position === 1 && buttonData.insert_element_selector ) { selector = buttonData.insert_element_selector; } else if (Object.keys(u.insert_button_info).length !== 0) { selector = u.insert_button_info.selector; insertType = u.insert_button_info.position; } else { switch (themeStoreId) { /* Dawn-887 Refresh-1567 Sense-1356 Crave-1363 Craft-1368 Studio-1431 Taste-1434 Ride-1500 Colorblock-1499 */ case 887: case 1567: case 1356: case 1363: case 1368: case 1431: case 1434: case 1500: case 1499: case 1399: { selector = ".card-information .price"; // selector = '.grid__item .card'; // const el = qa(selector); // el.forEach(eachEl => { // eachEl.style.height = 'unset'; // eachEl.style.position = 'relative'; // }); break; } case 829: { // Narrative selector = ".card__info>.card__price"; break; } case 775: { // Venture selector = ".product-card__info>.product-card__price"; break; } case 796: { // Debut selector = ".product-card>.price"; break; } case 730: { // Brooklyn selector = ".grid__item .grid-product__price-wrap"; break; } case 679: { // Supply selector = ".grid-item .product-item--price"; break; } case 380: { // Minimal selector = ".grid__item .grid-link__meta"; break; } case 578: { //Simple selector = ".grid__item .product__prices"; break; } case 857: { selector = ".grid__item .grid-product__price"; break; } case 765: { selector = ".product--details"; break; } case 849: { selector = ".product-block__info"; break; } case 459: { selector = ".product-info-inner .price"; break; } default: { // Warehouse主题 if (q("body.warehouse--v1")) { selector = ".product-item .product-item__price-list"; } else { switch (themeId) { // Insomniac主题 case 129555497130: selector = ".collection-grid-item__meta"; break; // energy主题 case 139843010841: selector = ".product-item-meta"; break; // Booster_3_0 case 117705834682: selector = ".grid-view-item"; break; case 3639214169: { selector = ".product-index .product-info"; break; } } if (shopId === 3639214169 && themeId === 139935711534) { selector = ".product-index .product-info"; } else if (shopId === 61058973863 && themeId === 130619900071) { selector = ".product-block .product-info"; } else if (shopId === 71594410265 && themeId === 143828386073) { selector = ".product-item__info-inner"; } else if (shopId === 68210557218 && themeId === 142585594146) { selector = ".card-information .price"; } else if (shopId === 25981404 && themeId === 132703387810) { selector = ".product-item-meta"; } else if (shopId === 66663121193 && themeId === 139562189097) { selector = ".product-item-meta"; } else if (shopId === 2797404227 && themeId === 122716946499) { selector = ".price.price--sold-out"; } else if (shopId === 73514025266 && themeId === 146007327026) { selector = ".product-item__price-list.price-list"; } else if (shopId === 25981404 && themeId === 132868636834) { selector = ".product-item__cta.button.button--secondary.hidden-phone"; } } } } } if (shopId === 72470462767 && themeStoreId === 1399) { selector = ".card-information__button"; insertType = "afterbegin"; } if (selector) { const nodes = Array.from(qa(selector)); if (nodes.length == 0) { await new Promise((res, rej) => { setTimeout(() => { res(); }, 1500); }); } return nodes.filter( (i) => i.offsetParent !== null && !i.getAttribute("bis-inserted") ); } else { return null; } } // 获取集合页产品列表的handle function getProsHandles(node) { let productHandleStrings = ""; let handleArr = []; let handle = ""; // if (nodes.length) { let tarEle = "A"; // for (let i = 0; i < nodes.length; i++) { if (node && node.tagName === tarEle && node.getAttribute("href")) { let href = node.getAttribute("href"); let start = href.lastIndexOf("/products/") + 10; let end = href.indexOf("?"); if (end != -1) { handle = href.substring(start, end); } else { handle = href.substring(start); } if (handle.indexOf(".jpg") === -1) { handle = decodeURI(handle); // handleArr.push(handle); } } // } // productHandleStrings = handleArr.join(',') // } // productHandleStrings = productHandleStrings.replace(/[#]/g, '') return handle; } // Mark 获取集合页产品A元素 function getProductsANodes(nodes) { let lists = { aNodeList: [], prosEle: [], }; const list = []; let aNodeList = []; let index = 0; // collTarget = Array.from(collTarget) let hs = 'a[href*="/products/"]'; let tarEle = "A"; let curNode = nodes[0]; let aNode; for (let i = 0; i < nodes.length; i++) { curNode = nodes[i]; aNode = null; for (let j = 0; j < 5; j++) { curNode = curNode.parentNode; if ( curNode.tagName === tarEle && curNode.href && curNode.href.indexOf("/products/") !== -1 ) { aNode = curNode; break; } let childA = q(hs, curNode); if (childA) { aNode = childA; break; } } if (aNode) { list[index] = { element: nodes[i], handle: getProsHandles(aNode), }; index++; lists.prosEle.push(nodes[i]); lists.aNodeList.push(aNode); } else { nodes[i] = null; } } return list; } // Mark 初始化产品信息 function initProductInfo() { /* * 初始化一些店铺的信息,比如 * variantData - 该产品所有的变体信息数组 * currentVariant - 当前/默认选中的变体,偶尔可能会与实际情况有差别 * available - 当前变体是否available * selectVariantId - 被选中的变体的id * hasAvailableV - 是否有缺货的变体 * unVariantOptions - 缺货的变体,用于之后生成弹窗中的select的options * currentVariantOption - 当前选中的变体 * addOptionsStatus - 似乎是用于记录是否选择了变体的状态 */ let info = { selectVariantId: "", unVariantOptions: [], currentVariantOption: null, addOptionsStatus: 0, productTitle: "", initUrl: document.URL, }; if (isProPage) { const variantData = JSON.parse(q("#em_product_variants").textContent); const hasAvailableV = variantData.some((v) => v.available === false); const currentVariant = JSON.parse( q("#em_product_selected_or_first_available_variant").textContent ); Object.assign(info, { variantData, currentVariant, available: currentVariant.available, selectVariantId: currentVariant.id, hasAvailableV, listenVariantFlag: true, }); } return info; } // Mark 初始化按钮和弹窗 function initBtnAndPopupData() { // 初始化按钮以及弹窗的相关数据 return { btnRadius: "", btnFontSize: "", inlineBtnWidth: "", inlineBtnHeight: "", btnFontWeight: "initial", popupStyleUrl: "getPopupStyle", buttonStyleUrl: "getButtonStyle", collButtonStyleUrl: "getCollectionButtonStyle", integrationUrl: "integrate/getIntegration", floatBtnPosition: "float-btn-right", buttonData: { inline_btn_text: "", inline_btn_bg_color: "", inline_text_color: "", inline_btn_margin_top: "", inline_btn_margin_bottom: "", collection_btn_value: "", collection_is_show: 0, is_active_inline_btn: 0, float_btn_text: "", float_btn_bg_color: "", float_text_color: "", customize_inline_btn_position: 0, insert_element_selector: "", insert_type: "", float_offset: 0, is_active_float_btn: 0, is_branding_removed: 0, }, generalData: { display_all: 0, // 只要有一个变体缺货,展示给所有变体 font_family: "inherit", font_size: "14", font_weight: "inherit", horizontal_animation: 0, hover_bg_color: "#333333", hover_text_color: "#ffffff", inline_btn_margin_top: "0", inline_btn_margin_bottom: "0", border_radius: "0", border_color: "transparent", customize_css: "", }, popupData: null, frameBtnColor: "#333333", frameBtnFontColor: "#ffffff", insertType: "afterend", insertEl: null, selectedType: {}, iti: null, // intl-phone-input插件 selBtnStatus: 0, // selBtnStatus返回情况 btnStyleSwitch: 0, // buttonStyle是否请求成功 popupStyleSwitch: 0, // popupStyle是否请求成功 inteStatus: 0, // 是否开启集成商 formAction: "https://" + document.domain + "/cart/add", // 用于验证form表单的action }; } function initElement() { return { inlineBtnElement: null, floatBtnElement: null, emailFrameElement: null, inlineEmailDiv: null, floatEmailDiv: null, invalidTip: null, successFrame: null, variantSelector: null, closeBox: null, submitBtn: null, soldOutBtn: null, emailInput: null, nameInput: null, smsInput: null, mailingCheckbox: null, trueForms: [], exactForm: null, }; } //Mark 获取按钮样式 function getBtnStyle(btn) { if (btn.tagName == "DIV") { btn = btn.querySelector("button"); } if (!btn) { return; } const btnStyle = window.getComputedStyle(btn, null); if (btnStyle.width == "auto" || !btnStyle.width) { inlineBtnWidth = ""; } else if (btnStyle.width.indexOf("px") !== -1) { if (parseFloat(btnStyle.width) > 120) { inlineBtnWidth = btnStyle.width; } } if (btnStyle.height == "auto" || !btnStyle.height) { inlineBtnHeight = ""; } else { inlineBtnHeight = btnStyle.height; } btnRadius = btnStyle.borderRadius; btnFontSize = btnStyle.fontSize; btnFontWeight = btnStyle.fontWeight; } // 获取soldout按钮以及样式 function getSoldOutBtn(trueForm) { const btnArr = trueForm.querySelectorAll("button"); const iptArr = [ ...trueForm.querySelectorAll("input[type='submit']"), ...trueForm.querySelectorAll("input[type='button']"), ]; const allArr = [...btnArr, ...iptArr]; if (allArr.length) { for (let i = 0; i < allArr.length; i++) { if ( (allArr[i].type == "submit" && allArr[i].name == "add") || (allArr[i].type == "submit" && allArr[i].name == "button") ) { soldOutBtn = allArr[i]; break; } } if (!soldOutBtn) { for (let i = 0; i < allArr.length; i++) { if (allArr[i].type == "submit") { soldOutBtn = allArr[i]; break; } } } if (!soldOutBtn) { for (let i = 0; i < allArr.length; i++) { if (allArr[i].disabled) { soldOutBtn = allArr[i]; break; } } } if (!soldOutBtn) { soldOutBtn = allArr[0]; } soldOutBtn && getBtnStyle(soldOutBtn); } } // 找shopify_payment_button以及parent function searchParentEl() { return new Promise((resolve) => { // 首先获取所有的form,并进行遍历 const forms = qa("form"); for (let i = 0; i < forms.length; i++) { // 如果当前表单的action与预测的formAction相同,则推入trueForms数组 if (forms[i].action.indexOf("/cart/add") !== -1) { trueForms.push(forms[i]); } } if (!trueForms.length) { resolve({ code: 501, msg: "Search el failed" }); } // 如果只有一个form表单的action与预期的相同,则一定为要添加按钮的form if (trueForms.length == 1) { exactForm = trueForms[0]; getSoldOutBtn(trueForms[0]); } else { // 对遍历得出的action符合预期的form数组再次进行循环 for (let i = 0; i < trueForms.length; i++) { if (soldOutBtn) { break; } const formStyle = window.getComputedStyle(trueForms[i], null); // 如果form不显示的话,直接中断本次循环,继续遍历之后的form表单 if ( formStyle.visibility != "visible" || formStyle.display == "none" || formStyle.height == "0px" || formStyle.height == "0" || formStyle.width == "0px" || formStyle.width == "0" || formStyle.height == "auto" ) { continue; } exactForm = trueForms[i]; getSoldOutBtn(trueForms[i]); // 有可能在第一次内层btnArr循环的时候已经取得了shopify_payment_button,要考虑父级是否有正常显示。 if (soldOutBtn) { const parent = soldOutBtn.parentElement; const parentStyle = window.getComputedStyle(parent, null); // 如果父级正常的话就结束trueForms循环 if ( parentStyle.visibility == "visible" && parentStyle.display != "none" && parentStyle.height != 0 && parentStyle.width != 0 ) { break; } } // 在该form中寻找是否存在shopify payment button,有的话结束循环,没有的话开始遍历所有按钮寻找对的按钮 soldOutBtn = trueForms[i].querySelector(payment_button_class); if (soldOutBtn) { break; } else { const iptSubArr = trueForms[i].querySelectorAll( "input[type='submit']" ); // 如果存在input的类型为submit,则将该按钮数组里第一个直接赋给soldOutBtn,并结束循环 if (iptSubArr.length != 0) { soldOutBtn = iptSubArr[0]; break; } // 如果不存在input的类型为submit,则获取form下的所有按钮进行遍历 const btnArr = trueForms[i].querySelectorAll("button"); for (let j = 0; j < btnArr.length; j++) { if (btnArr[j].type == "submit") { // 如果有类型为submit的按钮,直接将该按钮赋给soldOutBtn,并结束循环 soldOutBtn = btnArr[j]; // 注意,这里的break只是中断了当前的循环,外层循环还会继续。 break; } } } } // 循环结束 } if (soldOutBtn || exactForm) { const params = { code: 0, msg: "success" }; if (soldOutBtn) { insertType = "afterend"; insertEl = soldOutBtn; } else { insertType = "beforeend"; insertEl = exactForm; } resolve(params); } else if (times >= MAX_SEARCH_TIMES) { resolve({ code: 501, msg: "Search el failed" }); } else { times++; setTimeout(() => { searchParentEl().then((res) => resolve(res)); }, 50); } }); } function getParentWithoutForm() { // 找所有的有可能是add-to-cart按钮的类名,用循环判断按钮位置 const btnElements = qa(`.action-button, [class*=add-to-cart], [class*=add_to_cart], [id*=add_to_card], [id*=add-to-card], [data-add-to-cart], .sold-out, #out-of-stock-gl,.option-selectors`); if (btnElements.length) { for (let i = 0; i < btnElements.length; i++) { // 有的时候增加/减少产品数量的按钮可能也会被选进来,用宽度排除 const width = Number( window.getComputedStyle(btnElements[i], null).width.split("px")[0] ); // 如果按钮有宽度,而且宽度>64,则大概率不是 if (!isNaN(width) && width > 64) { return { type: "afterend", ele: btnElements[i] }; } } } const parents = qa( ".action-button, .tt-swatches-container.tt-swatches-container-js" ); if (parents.length) { for (let i = 0; i < parents.length; i++) { const style = window.getComputedStyle(parents[i], null); if ( style.visibility != "visible" || style.display == "none" || style.height == "0px" || style.height == "0" || style.width == "0px" || style.width == "0" || style.height == "auto" ) { continue; } return { type: "beforeend", ele: parents[i] }; } } return { type: "" }; } // 如果searchParentEl方法没有找到form function setInlineBtnWhenErr(el) { const res = getParentWithoutForm(); res.type && res.ele.insertAdjacentHTML(res.type, el); getBISEle(); } function handleSearchNodeFailed() { return new Promise((resolve) => { getButtonStyle(shopId, buttonStyleUrl).then(() => { if (!buttonData.insert_element_selector) { const newParentData = getParentWithoutForm(); if (!newParentData.type) { resolve({ code: 404 }); } else { resolve({ code: 0 }); } } else { resolve({ code: 0 }); } }); }); } // Mark 按钮自定义位置 function changeButtonPos() { /** * 用于更改按钮的位置/searchParentEl失败时指定parent */ const { customize_inline_btn_position, insert_element_selector, insert_type, } = buttonData; const isBlock = q("#sealapps-bis-widget"); if (isProPage && isBlock) { insertEl = isBlock; insertType = "beforeend"; } else { let selector = null; let position = null; if (customize_inline_btn_position === 1) { selector = insert_element_selector; position = insert_type; } else if (Object.keys(u.insert_button_info).length !== 0) { selector = u.insert_button_info.selector; position = u.insert_button_info.position; } if (selector && position) { if (isProPage) { insertEl = q(selector) || soldOutBtn; } else if (isCollPage) { insertEls = Array.from(qa(selector)); } insertType = position || "afterend"; } } } function handleBasicData() { if (soldOutBtn) { const parentStyle = window.getComputedStyle( soldOutBtn.parentElement, null ); if ( parentStyle.display == "flex" && parentStyle.flexDirection == "row" && parentStyle.flexWrap == "nowrap" ) { soldOutBtn.parentElement.style.flexWrap = "wrap"; } } const v1 = variantData[0]; try { const arr = v1.name.split("-"); if (arr.length === 1) { productTitle = v1.name; } else { if (arr.length === 2 && shopId === 77709705552) { productTitle = v1.name; } else { productTitle = arr.slice(0, -1).join("-"); } } productTitle = productTitle.trim(); if (shopId === 61058973863) { productTitle = v1.name; } else if (shopId === 55190913133) { productTitle = v1.name.split("-").slice(0, -1).join("-"); } } catch (error) { if (!v1.public_title) { productTitle = v1.name; } else { if (v1.public_title.length - 3 > 0) { productTitle = v1.name.substr( 0, v1.name.length - v1.public_title.length - 3 ); } else { productTitle = v1.name; } } } } // ## 获取所有数据 async function getAllData() { document.head.insertAdjacentHTML( "beforeend", '' ); emailStyle = document.querySelector(".email-style"); getPopupStyle(shopId, popupStyleUrl); getIntegration(shopId, integrationUrl); // if (isProPage) { // getButtonStyle(productPageType); // // requests.buttonSettings(productPageType); // } } function startRender() { if (getDataCount === 3) { // 产品页,在嵌入前发现已经嵌入过了,退出 if (isProPage) { const nodes = qa(`#product-restore-email-float,.product-restore-email`); if (nodes.length === 0) { renderBtnAndPopup(); } } else if (isCollPage) { renderBtnAndPopup(); } } } //Mark 请求按钮按钮样式接口 async function getButtonStyle(page_type) { if (btnStyleSwitch) { getDataCount++; return new Promise((resolve) => { resolve({ code: 0 }); }); } // 请求过了就return let isSuccess = false; let data = null; if ( updateStatus.is_button_settings_updating !== 1 && ((typeof bis_product_button_settings !== "undefined" && isProPage) || (typeof bis_collection_button_settings !== "undefined" && isCollPage)) ) { isSuccess = true; data = bis_product_button_settings; if (isCollPage) { data = bis_collection_button_settings; } } else { await requests.buttonSettings(page_type).then((res) => { if (res.code === 0 && res.data) { data = res.data; isSuccess = true; } }); } if (isSuccess) { btnStyleSwitch = 1; // inline设置 Object.keys(buttonData).forEach((key) => { buttonData[key] = data[key]; }); if (q("#sealapps-bis-widget")) { buttonData.is_active_inline_btn = 1; } Object.keys(generalData).forEach((key) => { generalData[key] = data[key]; }); if (isCollPage) { generalData.font_family = generalData.font_weight = "inherit"; } renderSettingStyles(); if (isProPage) { changeButtonPos(); } getDataCount++; startRender(); } } //Mark 请求弹窗样式接口 async function getPopupStyle(shopId, popupUrl) { // API路由 let isSuccess = false; if ( updateStatus.is_popup_settings_updating !== 1 && typeof bis_popup_settings !== "undefined" ) { popupStyleSwitch = 1; popupData = bis_popup_settings; isSuccess = true; } else { const res = await requests.popupSettings(); const { code, data } = res; if (code === 0 && data) { isSuccess = true; popupStyleSwitch = 1; popupData = JSON.parse(JSON.stringify(data)); } } if (isSuccess) { switch (popupData.popup_template) { case 1: selectedType.type = "email"; break; case 2: selectedType.type = "sms"; break; case 3: selectedType.type = "email"; break; } getDataCount++; startRender(); } } //Mark 请求集成情况接口 function getIntegration(shopId, inteUrl) { // API路由 if ( updateStatus.is_integrations_updating !== 1 && typeof bis_integrations !== "undefined" ) { inteStatus = bis_integrations.find((o) => o.is_enable === 1); getDataCount++; startRender(); } else { return requests.integrationStatus().then((res) => { const { code, data } = res; if (code === 0 && data) { // 只要有开启了的选项,就打开集成 inteStatus = data.list.find((o) => o.is_enable === 1); getDataCount++; startRender(); } else { getDataCount++; startRender(); } }); } } function renderSettingStyles() { const { font_size, horizontal_animation, border_radius, border_color, font_weight, font_family, inline_btn_margin_top, inline_btn_margin_bottom, hover_text_color, hover_bg_color, customize_css, } = generalData; let generalStyles = ` .email-me-button { font-size: ${font_size}px !important; font-weight: ${font_weight} !important; font-family: ${font_family} !important; border-color: ${border_color} !important; border-radius: ${border_radius}px !important; border-width: 2px; border-style: solid; } .email-me-button.email-me-submitButton { height:auto; min-height: 46px; } .email-me-button:hover { color: ${hover_text_color} !important; background-color: ${hover_bg_color} !important; } ${customize_css} `; if (horizontal_animation) { generalStyles += ` .email-me-inlineButton::after, .email-me-inlineButton::before, .email-me-submitButton::after, .email-me-submitButton::before { content:''; color: ${buttonData.inline_btn_bg_color}; font-size: ${font_size}px; text-align: center; border-radius: ${border_radius}px; width: 0; height: 100%; background-color: ${hover_bg_color}; position: absolute; left:0; transition: all ease-in-out .35s; top:0; z-index: -2; } .email-me-inlineButton::before, .email-me-submitButton::before { z-index: -1; background-color: ${hover_bg_color}; } .email-me-inlineButton:hover, .email-me-submitButton:hover { z-index: 1; color: ${hover_text_color} !important; background-color: ${hover_bg_color} !important; } .email-me-button:hover::before, .email-me-button:hover::after { width: 100%; } `; } const styles = ` `; document.head.insertAdjacentHTML("beforeend", styles); } // Mark 渲染弹窗 function renderBtnAndPopup() { // 预先创建没有库存的variantOptions const { toggler, ipt, mailingList } = renderSpecificPopup(); const mountWindowElement = `
success
${popupData.success_title}
${popupData.success_content}
${ popupData.is_branding_removed ? "" : `` }
`; document.body.insertAdjacentHTML("beforeend", mountWindowElement); const n = document.querySelector("#sealapps-bis-widget"); if (n && soldOutBtn) { if (getComputedStyle(soldOutBtn.parentNode).textAlign === "center") { emailStyle.textContent += "#sealapps-bis-widget{justify-content:center}"; } } renderButton().then((res) => { if (res.code === 0) { // 渲染按钮成功,进行下一步的操作 // changeButtonPos(); getBISEle(); // 获取所有需要进行操作的DOM元素 createEmailButton(); // 查询店铺是否开启按钮 listenVariantChange(); // 开始监听变体的变化 // 运行前执行用户需求自定义函数 if (typeof bisAfterExecute !== "undefined") { bisAfterExecute(v); } if (popupData.popup_template !== 1) { initSms(); // 初始化短信相关的操作 } } else { } }); } // Mark 渲染按钮 function renderButton() { // 根据开启类型渲染按钮 return new Promise((resolve, reject) => { const { is_active_inline_btn, is_active_float_btn } = buttonData; let flag = 0; if (is_active_inline_btn || isCollPage) { // 如果开启了inline,挂载inline const { inline_text_color, inline_btn_bg_color, inline_btn_text, collection_btn_value, inline_btn_margin_top, inline_btn_margin_bottom, } = buttonData; let btnText = inline_btn_text; const mountInlineBtn = `
${btnText}
`; if (isProPage) { try { console.log("insertEl", insertEl); insertEl.insertAdjacentHTML(insertType, mountInlineBtn); flag++; } catch (err) { setInlineBtnWhenErr(mountInlineBtn); flag++; } } else if (isCollPage) { insertEls.forEach((i, inx) => { iparentNode = i.parentNode; const isHaveBtn = qa(".product-restore-email", iparentNode); if (isHaveBtn.length == 0) { const wrapper = document.createElement("div"); wrapper.setAttribute("proId", collVariants[inx].proId); wrapper.innerHTML = mountInlineBtn; wrapper.style.position = "relative"; wrapper.style.zIndex = "1"; wrapper.className = "restore-email-wrapper"; i.insertAdjacentElement(insertType, wrapper); i.setAttribute("bis-inserted", true); } }); flag++; } } if (is_active_float_btn) { const { float_offset, float_text_color, float_btn_bg_color, float_btn_text, } = buttonData; const mountFloatBtn = `
`; document.body.insertAdjacentHTML("afterbegin", mountFloatBtn); flag++; } if (flag > 0) { resolve({ code: 0, msg: "Success!" }); } else { resolve({ code: 404, msg: "Insert failed" }); } }); } function renderSpecificPopup() { inlineBtnElement; /* * 根据用户开启的弹窗类型进行部分渲染 * 1 - 只开了邮件,渲染邮件输入框 * 2 - 只开了短信,渲染sms输入框 * 3 - 都开了,渲染两种输入框以及toggler(开关) */ let ipt, toggler, mailingList; console.log("popupData", popupData); type = popupData.popup_template; if (type === 1) { ipt = `
${popupData.verification_failed_text}
`; } else if (type === 2) { ipt = `
${popupData.verification_failed_text}
`; } else if (type === 3) { ipt = `
${popupData.verification_failed_text}
`; const email = `
${popupData.email_tab_text}
`; const sms = `
${popupData.sms_tab_text}
`; let content = ""; if (shopId === 56661573841) { content = sms + email; } else { content = email + sms; } toggler = `
${content}
`; } renderSpecificStyle(type); if (inteStatus) { mailingList = `
`; } return { ipt, toggler, mailingList, }; } function renderSpecificStyle(type) { switch (type) { case 1: case 2: addStyle( `` ); break; case 3: addStyle( `` ); break; default: break; } } //Mark 获取Back in stock相关的元素 function getBISEle() { switch (popupData.popup_template) { case 1: emailInput = q(".buyer-email"); break; case 2: smsInput = q(".buyer-phone"); break; case 3: emailInput = q(".buyer-email"); smsInput = q(".buyer-phone"); break; } nameInput = q(".email-frame-body .buyer-name"); successFrame = q(".successSub"); invalidTip = q(".invalid-email-tips"); emailFrameElement = q("#email-me-frame"); closeBox = q("#email-me-frame .close-box"); submitBtn = q(".frame-submit .email-me-button"); variantSelector = q(".selected-unavailable-variant"); inlineEmailDiv = qa(".product-restore-email"); floatEmailDiv = q("#product-restore-email-float"); inlineBtnElement = qa(".email-me-inlineButton"); floatBtnElement = q(".email-me-floatButton"); mailingCheckbox = q("#join-mailing-list") || {}; insertStyle = q("#email-insert-style"); // 获取完了各个元素之后开始进行事件的添加 handleEleEvent(); } // Mark 控件添加事件 function handleEleEvent() { // 对各个元素进行事件处理与绑定 switch (popupData.popup_template) { case 1: emailInput.addEventListener("blur", verifyEmail); break; case 2: break; case 3: emailInput.addEventListener("blur", verifyEmail); break; } submitBtn.addEventListener("click", subEmail); // submitBtn.addEventListener('click', debounce(500, subEmail)); closeBox.addEventListener("click", function () { emailFrameElement.style.display = "none"; if (variantSelector.style.display !== "none") { currentVariantOption && currentVariantOption.removeAttribute("selected"); } }); successFrame.addEventListener("click", function () { successFrame.classList.remove("successSub_active"); }); if (isProPage) { mountedUnVariantOptions(); } initInlineAndFloatBtn(); } // Mark 添加按钮事件 function initInlineAndFloatBtn() { if (inlineBtnElement.length) { inlineBtnElement.forEach((i, inx) => { i.addEventListener("click", function (e) { e.preventDefault(); autoInput(); let curProId = 0; emailFrameElement.style.display = "block"; const selected_unavailable_variant = emailFrameElement.querySelector( ".selected-unavailable-variant" ); if (isCollPage) { curProId = this.parentNode.parentNode.getAttribute("proId"); let oldProId = selected_unavailable_variant.getAttribute("proId"); if (oldProId) { if (oldProId !== curProId) { addOptionsStatus = 0; variantData = collVariants.find( (i) => i.proId == curProId ).variants; unVariantOptions = []; mountedUnVariantOptions(); selected_unavailable_variant.innerHTML = ""; selected_unavailable_variant.setAttribute("proId", curProId); } } else { unVariantOptions = []; variantData = collVariants.find( (i) => i.proId == curProId ).variants; mountedUnVariantOptions(); selected_unavailable_variant.setAttribute("proId", curProId); } } // 挂载没有库存的variant option for (let i = 0; i < unVariantOptions.length; i++) { if (addOptionsStatus === 0) { selected_unavailable_variant.add(unVariantOptions[i]); } if ( unVariantOptions[i].getAttribute("value") === selectVariantId.toString() ) { currentVariantOption = selected_unavailable_variant.querySelectorAll("option")[i]; currentVariantOption.setAttribute("selected", "selected"); } } addOptionsStatus = 1; }); }); // inlineBtnElement.addEventListener('click', function () { // autoInput(); // emailFrameElement.style.display = 'block'; // // 挂载没有库存的variant option // const selected_unavailable_variant = emailFrameElement.querySelector('.selected-unavailable-variant'); // for (let i = 0; i < unVariantOptions.length; i++) { // if (addOptionsStatus === 0) { // selected_unavailable_variant.add(unVariantOptions[i]); // } // if (unVariantOptions[i].getAttribute('value') === selectVariantId.toString()) { // currentVariantOption = selected_unavailable_variant.querySelectorAll('option')[i]; // currentVariantOption.setAttribute('selected', 'selected'); // } // } // addOptionsStatus = 1; // }); } if (floatBtnElement) { floatBtnElement.addEventListener("click", function () { autoInput(); emailFrameElement.style.display = "block"; // 挂载没有库存的variant option // const selected_unavailable_variant = emailFrameElement.querySelector('.selected-unavailable-variant'); for (let i = 0; i < unVariantOptions.length; i++) { if (addOptionsStatus === 0) { variantSelector.add(unVariantOptions[i]); } if ( unVariantOptions[i].getAttribute("value") === selectVariantId.toString() ) { currentVariantOption = variantSelector.querySelectorAll("option")[i]; currentVariantOption.setAttribute("selected", "selected"); } } addOptionsStatus = 1; }); } } // 自动填入登录用户的信息 function autoInput() { if (JSON.stringify(customerInfo) != "{}") { nameInput.value = customerInfo.name; if (emailInput) { emailInput.value = customerInfo.email; } if (smsInput) { smsInput.value = customerInfo.phone.replace(/[^0-9]/g, ""); } } } // Mark 生成弹窗变体下拉框选项 function mountedUnVariantOptions() { let optionIndex = 0; let title, id = "id"; if (isProPage) { title = "title"; } else if (isCollPage) { title = "title"; id = "variant_id"; } if (isCollPage) { setProName(variantData[0].product_id); } if (variantData.length === 1 && variantData[0].title === "Default Title") { variantSelector.style.display = "none"; } else { variantSelector.style.display = "block"; } for (let i = 0; i < variantData.length; i++) { // 如果variantData存在此变体id则该变体需要展示按钮 if ( (showVariants.includes(String(variantData[i].id)) && isProPage) || isCollPage ) { unVariantOptions[optionIndex] = create({ tag: "option", attributes: { value: variantData[i][id], textContent: variantData[i][title], }, }); optionIndex++; } } } //Mark 防抖监听集合页元素变动 async function collPageObserve() { observer.disconnect(); const curEls = await getProsEle(); const bns = qa(".product-restore-email").length; if ((curEls !== null && curEls.length !== oldCollElsCount) || !bns) { // collVariants = []; insertEls = []; selBtnStatus = 0; getDataCount = 0; await init(); execute(); } else { checkVariantChange(); } } // Mark 监听变体切换 function listenVariantChange() { /** * 该方法主要用于判断使用什么方法监听变体的变化 * 1. 当url中包含variant=的时候,采用listen url的方法 * 2. 当url中不包含的时候,采用定时器的方法 */ if (isProPage) { const url = document.URL; listenUrlStatus(); if ( url.indexOf("variant=") === -1 || shopId == 1742274613 || shopId == 50606899391 || shopId === 72936030549 ) { checkVariantChange(); } } else if (isCollPage) { setTimeout(() => { checkVariantChange(); }, 1000); } } //Mark 当观察到变动时执行的回调函数 function setCollDeb() { observerCallback = debounce(400, function () { if (isProPage) { let curVariantId; if (shopId == 55013703857) { const selectedTitle = q(".select-selected").innerText; curVariantId = variantData.find((o) => o.title == selectedTitle).id; } else if (shopId == 1742274613) { const option1Node = q(".option-0"); const option2Node = q(".option-1"); const option1Title = option1Node.querySelector(".selected_val").innerText; const option2Title = option2Node.querySelector(".selected_val").innerText; curVariantId = variantData.find( (o) => o.option1 == option1Title && o.option2 == option2Title ).id; } else if (shopId === 56342347836) { const ns = q(".t4s-product__select.t4s-d-none"); curVariantId = ns.value; } else if (shopId === 72936030549) { curVariantId = variantData.find( (it) => it.title === q(".swatch input:checked").value ).id; } else { curVariantId = q("input[name=id], select[name=id]").value; } handleVariantChange(curVariantId); } else if (isCollPage) { collPageObserve(); } }); } // Mark 产品页变体切换监听 function checkVariantChange() { // 选择要观察变动的节点 let targetNode; if (isProPage) { if (shopId == 55013703857) { targetNode = q(".select-selected, select[name=id]"); } else if (shopId == 1742274613) { targetNode = q(".option-1"); } else if (shopId == 42547151016 && themeId === 122478428328) { targetNode = q(".swatch-variants-wrapper"); } else if (shopId == 57593233596 && themeId === 132009820348) { targetNode = q(".swatches-select.swatch__list_pr"); } else if (shopId == 16708787 && themeId === 141528334609) { targetNode = q(".shopify-product-form"); } else if (shopId == 56342347836 && themeId === 160444678479) { targetNode = q("form .t4s-swatch"); } else if (shopId == 23566876752 && themeId === 122101039184) { targetNode = q(".swatches-select"); } else if (shopId == 63893045484 && themeId === 136267792620) { targetNode = q(".option-selectors"); } else if (shopId == 55224107147 && themeId === 123578253451) { targetNode = q(".product-select-simple-wrapper"); } else if (shopId == 50606899391 && themeId === 147023823194) { targetNode = q(".ProductForm__Variants"); } else { switch (shopId) { case 78542635323: { if (themeId === 158288609595) { targetNode = q(".t4s-swatch__list"); } break; } case 72936030549: { if (themeId === 147312345429) { targetNode = q(".maxus-productdetail__options"); } break; } default: { if (v.variantListenerElement) { targetNode = q(v.variantListenerElement); } else { targetNode = q("input[name=id], select[name=id]"); } } } } if (!targetNode) { targetNode = q("input[name=id], select[name=id]"); } } else if (isCollPage) { targetNode = q("main"); if (shopId === 25981404 && themeId === 132868636834) { targetNode = q("#main"); } } if (targetNode) { // 观察器的配置l const config = { attributes: true, childList: true, subtree: true }; // 创建一个观察器实例并传入回调函数 observer = new MutationObserver(observerCallback); // 以上述配置开始观察目标节点 observer.observe(targetNode, config); } } function listenUrlStatus() { overwritePushstate(); window.addEventListener("locationchange", () => { const currentUrl = document.URL; // 如果之后开始要对collection页展示按钮了,可能会用到下面两行 // const url = new URL(currentUrl); // const isVariantUrl = url.searchParams.get('variant'); if (currentUrl !== initUrl) { const currentUrl = document.URL; const url = new URL(currentUrl); const vid = url.searchParams.get("variant"); initUrl = currentUrl; vid && handleVariantChange(vid); listenVariantFlag = false; } }); } function handleVariantChange(vid) { /** * TODO 当切换变体后url中开始有variant=了 * 取消listen变体的定时器 */ if (!vid) return; if (String(selectVariantId) !== String(vid)) { selectVariantId = vid; currentVariant = variantData.find((o) => o.id == vid); available = currentVariant.available; if ( (showVariants.includes(String(selectVariantId)) && !btnStyleSwitch) || !popupStyleSwitch ) { getAllData(); } if (showVariants.includes(String(selectVariantId)) && !selBtnStatus) { createEmailButton(); } if (selBtnStatus === 1) { initEmailToMeElement(); } } } //Mark 查询店铺订阅按钮的状态 async function createEmailButton() { if (selBtnStatus === 0) { // 还未成功请求服务器 // url后缀 let isSuccess = false; let data = null; if ( updateStatus.button_status === 2 && typeof bis_button_status !== "undefined" ) { isSuccess = true; data = bis_button_status; } else { // const url = baseUrl + "api/v1/email/selBtnStatus"; // await request(url, { shopId }).then((res) => { // if (res.code === 0) { // isSuccess = true; // data = res.data; // } // }); isSuccess = true; data = { status: 1 }; } if (isSuccess) { if ( data.status == 1 || data.status == 2 || data.status == 0 || data.snsStatus ) { selBtnStatus = 1; initEmailToMeElement(); } else { selBtnStatus = data.status; } } } } //Mark 初始化按钮 function initEmailToMeElement() { // 移除了displayforall的判断,统一由后端判断该变体需不需要展示 if (generalData.display_all === 1) { if (inlineBtnElement) { inlineBtnElement.forEach((i) => (i.style.display = "flex")); inlineEmailDiv.forEach((i) => (i.style.display = "flex")); } if (floatBtnElement) { floatBtnElement.style.display = "flex"; floatEmailDiv.style.display = "flex"; } return; } if ( selBtnStatus === 1 && ((showVariants.includes(String(selectVariantId)) && isProPage) || isCollPage) ) { customFeature && customFeature(); if (inlineBtnElement) { inlineBtnElement.forEach((i) => (i.style.display = "flex")); inlineEmailDiv.forEach((i) => (i.style.display = "flex")); } if (floatBtnElement) { floatBtnElement.style.display = "flex"; floatEmailDiv.style.display = "flex"; } } else { if (inlineBtnElement) { // inlineBtnElement.style.display = "none"; inlineEmailDiv.forEach((i) => { i.style.display = "none"; }); } if (floatBtnElement) { // floatBtnElement.style.display = 'none'; floatEmailDiv.style.display = "none"; } } } //Mark 校验邮件格式 function verifyEmail() { const email = emailInput.value; const reg = new RegExp( /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,}$/ ); if (!reg.test(email)) { toggleInvalidTip(true, { type: "email", info: popupData.verification_failed_text, }); } else { invalidTip.style.visibility = "hidden"; } } //Mark 创建element function create({ tag, appendTo, children = [], attributes = {}, events = {}, }) { const element = document.createElement(tag); Object.entries(attributes).forEach(([key, value]) => { element[key] = value; }); Object.entries(events).forEach(([key, value]) => { element.addEventListener(key, value); }); if (appendTo) { appendTo.appendChild(element); } children.forEach((child) => element.appendChild(child)); return element; } //Mark 提交订阅 selectVariantId function subEmail() { console.log("subEmail"); const { verification_failed_text } = popupData; let buyerName; if (nameInput) { buyerName = nameInput.value; } console.log("selectedType.type", selectedType.type); switch (selectedType.type) { case "sms": const sms = (smsInput && smsInput.value) || ""; if (!sms) { toggleInvalidTip(true, { type: "sms", info: verification_failed_text, }); } else { if (iti.isValidNumber()) { toggleInvalidTip(false); subscribeSms({ buyerName }); } else { toggleInvalidTip(true, { type: "sms", info: verification_failed_text, }); } } break; case "email": const email = emailInput.value; console.log("email", email); if (!email) { console.log(5555); toggleInvalidTip(true, { type: "email", info: verification_failed_text, }); } else { // 判断邮件的格式是否正确 const reg = new RegExp( /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,}$/ ); if (reg.test(email)) { toggleInvalidTip(false); subscribeEmail({ buyerName }); } else { toggleInvalidTip(true, { type: "email", info: verification_failed_text, }); } } break; default: return; } } function toggleInvalidTip(show, data) { /** * 函数的本意是为了开关invalid提示,但是如果设置了show的话就是为了手动隐藏/关闭 * show - 展示/隐藏提示 * data.type - 当前提示的类型 * data.info - 当前提示的信息 */ const style = getComputedStyle(invalidTip); const { type, info } = data || { type: selectedType.type, info: popupData.verification_failed_text, }; switch (type) { case "sms": invalidTip.innerHTML = info; if (style.visibility === "hidden") { invalidTip.style.visibility = "visible"; } else { invalidTip.style.visibility = "hidden"; } break; case "email": invalidTip.innerHTML = info; if (style.visibility === "hidden") { invalidTip.style.visibility = "visible"; } else { invalidTip.style.visibility = "hidden"; } break; default: invalidTip.style.visibility = "hidden"; break; } if (show) { invalidTip.style.visibility = "visible"; } else if (show === false) { invalidTip.style.visibility = "hidden"; } } // Mark 发送短信 function subscribeSms(data) { // 传递的参数 const params = { shopId: shopId, phone: formatPhoneNumber(smsInput.value.trim()), phone_region: iti.getSelectedCountryData().iso2.toUpperCase(), is_integration: Number(mailingCheckbox.checked || false), variant_id: variantSelector.value, subscriber_name: data.buyerName || "customer", lang: locale, }; const url = bisBaseUrl + "api/v1/product/add_customer_stock_subscription"; submitBtn.parentElement.className = "frame-submit loading"; request(url, params) .then((res) => { const { code, message } = res; if (code === 0) { emailFrameElement.style.display = "none"; successFrame.classList.add("successSub_active"); setTimeout(function () { successFrame.classList.remove("successSub_active"); }, 4000); } else if (code === 6 || code === 10) { // 新增订阅失败 invalidTip.style.visibility = "visible"; invalidTip.innerHTML = popupData.subscribed_text; } else if (code === 3) { invalidTip.style.visibility = "visible"; invalidTip.innerHTML = popupData.verification_failed_text; } else { invalidTip.style.visibility = "visible"; invalidTip.innerHTML = message; } }) .finally(() => { submitBtn.parentElement.className = "frame-submit"; }); } // Mark 订阅邮件 function subscribeEmail(data) { // url后缀 // 传递的参数 const params = { variant_id: variantSelector.value, email: document.getElementsByClassName("buyer-email")[0].value, subscriber_name: data.buyerName || "customer", is_integration: Number(mailingCheckbox.checked || false), langg: locale, }; const url = bisBaseUrl + "api/v1/product/add_customer_stock_subscription"; submitBtn.parentElement.className = "frame-submit loading"; request(url, params) .then((res) => { const { code, message } = res; if (code === 0) { emailFrameElement.style.display = "none"; successFrame.classList.add("successSub_active"); setTimeout(function () { successFrame.classList.remove("successSub_active"); }, 4000); } else if (code === 6 || code === 10) { // 新增订阅失败 invalidTip.style.visibility = "visible"; invalidTip.innerHTML = popupData.subscribed_text; } else if (code === 3) { invalidTip.style.visibility = "visible"; invalidTip.innerHTML = popupData.verification_failed_text; } else { invalidTip.style.visibility = "visible"; invalidTip.innerHTML = message; } }) .finally(() => { submitBtn.parentElement.className = "frame-submit"; }); } function formatPhoneNumber(num) { const code = iti.getSelectedCountryData().dialCode; //把开头为0的过滤 num = num.replace(/\b(0+)/gi, ""); if (!num.startsWith(code)) { // 如果电话不是以区号开头的,直接拼接区号并返回 return code + num; } else { return num; } } function initSms() { const emailInput = q(".buyer-email"); // const phoneInput = q('.buyer-phone-block'); const phoneInput = q(".buyer-phone"); const phoneContainer = q(".buyer-phone-container"); // const countrySelector = q('.country-selector'); // const countryList = q('.country-selector-list'); const emailTypeBtn = q(".email-type"); const smsTypeBtn = q(".sms-type"); const regionFlag = q(".iti--allow-dropdown"); if (!regionFlag) { initPhoneInput().then((res) => { if (res.code === 0 && popupData.popup_template === 3) { selectedType = new Proxy( { type: "email" }, { set(target, key, newVal) { target[key] = newVal; toggleInput(newVal); return true; }, } ); toggleInput(selectedType.type); emailTypeBtn.addEventListener("click", () => { selectedType.type = "email"; }); smsTypeBtn.addEventListener("click", () => { selectedType.type = "sms"; }); } }); } function initPhoneInput() { /* * 在这里引入了插件intl-tel-input * 具体的使用方法可以在这两个地方看 * npm: https://www.npmjs.com/package/intl-tel-inpu * github: https://github.com/jackocnr/intl-tel-input#getting-started-not-using-a-bundler * 在这里有对应的全套cdn https://cdnjs.com/libraries/intl-tel-input */ return new Promise((resolve) => { const cssUrl = "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.16/css/intlTelInput.css"; addScript(cssUrl).then((cssRes) => { if (cssRes.code === 0) { // 国旗是png格式的精灵图,也是由cdn引入的,具体看下面resetFlag中的参数 const resetFlag = ` `; document.head.insertAdjacentHTML("beforeend", resetFlag); // 确保插件的css引入了之后再进行js的引入 addScript( "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.16/js/intlTelInput.min.js", true ).then((script) => { // 获得创建的script元素,再将script元素引入之前先对其进行 script.onload = function () { iti = window.intlTelInput(phoneInput, { utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.16/js/utils.min.js", autoPlaceholder: "aggressive", initialCountry: popupData.sms_default_region || "", }); phoneInput.addEventListener("input", (e) => { iti.isValidNumber() ? toggleInvalidTip(false) : toggleInvalidTip(true); }); phoneInput.addEventListener("blur", (e) => { iti.isValidNumber() ? toggleInvalidTip(false) : toggleInvalidTip(true); }); }; document.body.appendChild(script); resolve({ code: 0 }); }); } }); }); } function toggleInput(type) { // 切换Email / SMS选项时进行操作 invalidTip.style.visibility = "hidden"; switch (type) { case "sms": emailInput.style.display = "none"; phoneContainer.style.display = "flex"; emailTypeBtn.className = "email-type"; smsTypeBtn.className = "sms-type type-selected"; // mailingCheckbox.parentElement.style.display = 'none'; break; case "email": smsTypeBtn.className = "sms-type"; emailInput.style.display = "block"; phoneContainer.style.display = "none"; emailTypeBtn.className = "email-type type-selected"; // mailingCheckbox.parentElement.style.display = 'flex'; break; default: invalidTip.style.visibility = "hidden"; break; } } } // 封装引入js, css的函数 function addScript(url, returnWithScript = false) { return new Promise((resolve, reject) => { try { const type = (url.endsWith(".js") && "js") || "css"; if (type === "js") { const script = document.createElement("script"); script.setAttribute("type", "text/javascript"); script.setAttribute("src", url); if (returnWithScript) { resolve(script); } document.head.appendChild(script); resolve({ code: 0, data: script, type: "script" }); } else if (type === "css") { const link = document.createElement("link"); link.setAttribute("rel", "stylesheet"); link.setAttribute("href", url); if (returnWithScript) { resolve(link); } document.head.appendChild(link); resolve({ code: 0, data: link, type: "style" }); } } catch (err) { reject({ code: 600, err }); } }); } /** * @function createRequestId 创建RequestId用于链路追踪 * @param {integer} length 希望生成的字符串长度 * @returns {string} 指定长度的字符串 */ function createRequestId(length) { // length边界处理 length = length && length < 64 ? length : 63; let result = ""; const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"; for (let i = 0; i < length; ++i) { result += chars[Math.floor(Math.random() * chars.length)]; } const timestamp = +new Date(); return result + timestamp; } function request(url, params, method = "POST", callback) { /** * 封装请求函数 * @param(url) - api请求地址,必选。 * @param(params) - 请求参数,可选。 * @param(callback) - 回调函数,可选。没有回调函数也会resolve获得到的数据 * @param(method) - 请求方法,可选。 * @returns Promise */ return new Promise((resolve, reject) => { if (!url) { resolve({ code: 999, data: "没有传api地址" }); } try { // 有params就拆params,不然就给个空对象方便请求 const finalParams = params || {}; if ((params && !Object.keys(params).includes("shopId")) || !params) { finalParams.shop_id = shopId; // 参数中没给shopId就给一下 } const xmlHttp = new XMLHttpRequest(); if (method === "GET") { url += objectToQuery(params); } // post请求方式 xmlHttp.open(method, url, true); if (method === "POST") { // 添加http头,发送信息至服务器时的内容编码类型 xmlHttp.setRequestHeader("Content-Type", "application/json"); if (url.includes("emailnoticeapi")) { xmlHttp.setRequestHeader("authorization", domain); // 头部新增 request id 链路追踪 xmlHttp.setRequestHeader("Org-Request-ID", createRequestId(37)); // 头部新增 请求页面 xmlHttp.setRequestHeader("Org-Request-URL", window.location.href); } } xmlHttp.send(method === "POST" ? JSON.stringify(finalParams) : null); // 发送数据 xmlHttp.onreadystatechange = function () { // 请求完成 if ( (xmlHttp.readyState == 4 && xmlHttp.status == 200) || xmlHttp.status == 304 ) { // 从服务器上获取数据 const json = JSON.parse(this.responseText); const { code, data } = json; if (code === 0) { if (callback) { callback(data); } resolve(json); } else if (code !== 500) { resolve(json); } } }; } catch (err) { reject(err); } }); } async function getUserNeedData() { //Mark 获取用户需求数据 if (isCollPage || isProPage) { let needData, needErr; if (typeof bis_custom_info !== "undefined") { needData = bis_custom_info; needErr = null; } if (needErr === null) { // 移动评论区信息 for (let k in needData) { const list = needData[k]; const len = list.length; if (len !== 0) { for (let i = 0; i < list.length; i++) { const item = list[i]; const tp = item.page || item.target_page; const tid = item.theme_id; if (tp) { if (tp.indexOf(pageType) === -1 && tp !== "all") { continue; } } if (tid && themeId != tid) { continue; } if (Object.keys(u[k]).length !== 0) { u[k] = item; } else if (Object.keys(u[k]).length === 0) { u[k] = item; } } } } } } } // Mark获取需要展示按钮的商品 async function getProductStatus(baseApiUrl) { let productIds = []; if (window.ShopifyAnalytics && ShopifyAnalytics.meta.product) { const productId = ShopifyAnalytics.meta.product.id; productIds.push(productId); } else { return; } const params = { shopId, product_ids: productIds, type: 2, }; const url = baseApiUrl + "api/v1/customer/getVariantBtStatus"; await request(url, params).then((res) => { const { apiCode, products } = res; if (apicode === 0 && products) { //由于是产品页,所以产品肯定只有一个 showVariants = products[0].variants.map((variant) => { return variant.variant_id; }); } }); } // inject css 样式 function importStyles() { const styles = ``; document.head.insertAdjacentHTML("beforeend", styles); document.head.insertAdjacentHTML( "beforeend", `` ); } function addStyle(style) { if (typeof style === "string") { document.head.insertAdjacentHTML("beforeend", style); } else { document.head.appendChild(style); } } // 获取url参数 function getQueryString(name) { const reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i"); const r = window.location.search.substr(1).match(reg); if (r != null) { return unescape(r[2]); } return null; } function changeStatus(data) { const emailCustomerId = getQueryString("emailCustomerId"); if (!emailCustomerId) { return; } const variantId = getQueryString("variant"); if (!variantId) { return; } const { baseUrl } = data; // 传递的参数 const params = { id: emailCustomerId, shopId, variantId, }; // API路由 const url = baseUrl + "api/v1/email/changeEmailStatus"; request(url, params); } function overwritePushstate() { const oldPushState = history.pushState; history.pushState = function pushState() { const ret = oldPushState.apply(this, arguments); window.dispatchEvent(new Event("pushstate")); window.dispatchEvent(new Event("locationchange")); return ret; }; const oldReplaceState = history.replaceState; history.replaceState = function replaceState() { const ret = oldReplaceState.apply(this, arguments); window.dispatchEvent(new Event("replacestate")); window.dispatchEvent(new Event("locationchange")); return ret; }; window.addEventListener("popstate", () => { window.dispatchEvent(new Event("locationchange")); }); } function q(selector, context) { let node; if (context) { node = context.querySelector(selector); } else { node = document.querySelector(selector); } return node; } function qa(selector, context) { let nodes; if (context) { nodes = context.querySelectorAll(selector); } else { nodes = document.querySelectorAll(selector); } return nodes; } function shopLanguageCallback() { const languageSelector = q(".notranslate"); const languageEl = languageSelector.querySelector(".selected img[alt]"); locale = languageEl.alt; } function setProName(curProId) { productTitle = collVariants.find((i) => i.proId == curProId).productName; const productTitleEl = q(".frame-body-content"); productTitleEl.innerText = productTitle; } /** * 将对象转换为 URL 查询字符串 * @param {Record} obj - 要转换的对象 * @returns {string} 转换后的查询字符串 * @example * const params = { * name: 'John', * age: 25, * tags: ['developer', 'js'] * }; * const query = objectToQuery(params); * // 输出: name=John&age=25&tags=developer&tags=js */ function objectToQuery(obj) { const params = new URLSearchParams(); for (const [key, value] of Object.entries(obj)) { // 如果是数组,需要特殊处理 if (Array.isArray(value)) { value.forEach((item) => params.append(key, item)); } else if (value !== null && value !== undefined) { params.append(key, value.toString()); } } return "?" + params.toString(); } // Mark 用户需求 function addCustomModify() { switch (shopId) { case 55605198922: { customStyle += `#email-me-frame .frame-title, #email-me-frame input { font-family: 'Sabon Next'; }`; break; } case 66366374137: { if (isCollPage) { if (isMobile) { customStyle += `@media only screen and (min-width: 280px) and (max-width: 757px) { .product-restore-email{ max-width: initial !important; } .email-me-inlineButton{line-height: calc(1 + .2 / var(--font-body-scale));height:auto !important;padding: 6px;}}`; } customStyle += `.quick-add__submit[disabled] { display: none; }.email-me-inlineButton{min-height: calc(4.5rem + var(--buttons-border-width) * 2);}`; } break; } case 27251892: { if (isCollPage) { customStyle += `.email-me-button.email-me-inlineButton { width: calc(100% - 100px); margin: 0 auto; top: 5px; }`; } break; } case 2797404227: { if (isCollPage) { customStyle += `#product-grid .grid__item:has(.price-item--sale) { margin-bottom: 25px; }`; } break; } case 31035482: { if (isCollPage) { executeDelay = 1000; customStyle += `#shopify-section-collection-template .collection-products-wrapper { margin-bottom: 60px; }@media only screen and (min-width: 768px){.restore-email-wrapper { top: -15px; padding: 0 15px; }}`; } break; } case 66697953568: { if (isProPage) { customFeature = function () { const n = document.querySelector( ".product_payments_btns .shopify-payment-button__button[disabled]" ); if (n) { n.closest(".product_payments_btns").style.display = "none"; } const available = document.querySelectorAll( ".swatch.clearfix .available" ); const soldout = document.querySelectorAll( ".swatch.clearfix .soldout" ); soldout.forEach((it) => (it.style.display = "none")); if (available.length === 0) { document.querySelector(".swatch.clearfix").style.display = "none"; } }; } } case 57581600963: { customStyle += ` #email-me-frame{font-family: synthese, sans-serif !important;} #email-me-frame .frame-title { padding-left: 0px !important; color: #0d3860 !important; font-size: 11px !important; font-weight: 400 !important; padding-top: 3px !important; text-transform: capitalize; } #email-me-frame .frame-body-content { font-weight: 300 !important; font-size: 11px !important; color: #0d3860 !important; } button .email-me-inlineButton { font-weight: 400 !important; border-width: 1px; }`; if (isCollPage) { customStyle += ` #email-me-frame .email-frame-content { border-radius: 0px !important; border: 0 !important; box-shadow: none !important; } #email-me-frame .frame-title { padding-left: 0px !important; font-family: synthese, sans-serif !important; color: #0d3860 !important; font-size: 16px !important; font-weight: 400 !important; padding-top: 0px; padding-bottom: 6px; text-transform: capitalize; }`; } } case 73514025266: { if (themeId === 146007327026 && isCollPage) { customStyle += `.product-restore-email {width: 80% !important;margin: 0 auto;}`; } break; } case 58193838255: { if (isMobile && isCollPage) { customStyle += `.email-me-button.email-me-inlineButton { letter-spacing: 0px; }`; } break; } case 53793915066: { if (themeId === 136421834975) { customStyle += `.product-restore-email{max-width:initial !important}`; } break; } case 23566876752: { if (themeId === 122101039184) { customStyle += `.rtl_true #email-me-frame .iti__flag-container{direction:ltr}.rtl_true #email-me-frame .iti__flag-container ul{direction:rtl}.rtl_true #email-me-frame .iti__flag-container .iti__flag-box{margin-left:6px}`; } break; } case 61285925123: { customStyle += `#email-me-frame div.frame-title { padding: 0 13px 0 0; flex: initial; }div.notify-type-toggler > div:nth-child(2) { border-left: 1px solid var(--sa-border-color); }.restore-email-wrapper { width: 100%; top: -30px; }.collection-grid__wrapper .grid-item.grid-product { flex-wrap: wrap; }#email-me-frame div.email-frame-header { justify-content: initial !important; padding: 0 30px 0 0; }.email-frame-body input::placeholder { opacity: 1; }#email-me-frame .email-footer-tips span { color: #222; }#email-me-frame .frame-close { margin-right: 0 !important; }#email-me-frame .close-box { margin: 0 !important; }`; break; } case 11881800: { customFeature = function () { insertStyle.textContent += `button.custom_pers.product-add-to-cart-btn { background-color: #fff !important; color: #000; }`; inlineEmailDiv[0].insertAdjacentHTML( "beforebegin", `
GET NOTIFIED WHERE BACK IN STOCK
` ); }; } case 67088286014: { customFeature = function () { const frame = q("#email-me-frame"); if (frame) { q(".frame-email-logo").innerHTML = ` `; } }; break; } case 52260241573: { customFeature = function () { const n = q("#email-me-frame .frame-submit"); if (n) { n.insertAdjacentHTML( "afterend", `

By signing up, you agree to receive promotional and marketing information from Mango People

` ); } }; break; } case 56661573841: { customFeature = function () { if (document.querySelector(".sms-type")) { let me = new Event("click"); document.querySelector(".sms-type").dispatchEvent(me); } }; break; } case 71669449038: { customFeature = function () { qa(".email-frame-body input").forEach((i) => i.removeAttribute("type") ); }; break; } case 72470462767: { if (isProPage) { customFeature = function () { // Mark 产品页变体切换监听 // 选择要观察变动的节点 let targetNode; if (shopId == 72470462767 && themeId === 158979948847) { targetNode = q( "section.page-width.section--padding.product.productSection" ); if (targetNode) { // 观察器的配置 const config = { attributes: true, childList: true, subtree: true, }; // 创建一个观察器实例并传入回调函数 const observer = new MutationObserver( debounce(500, () => { setTimeout(async () => { const n = q(".product-restore-email"); if (!n) { soldOutBtn = null; insertEl = null; selBtnStatus = 0; insertStyle.textContent += `.email-me-button.email-me-inlineButton { height: 60px !important; }`; qa("#email-me-frame,.successSub").forEach((it) => it.remove() ); await init(); execute(); observer.disconnect(); } }, 400); }) ); // 以上述配置开始观察目标节点 observer.observe(targetNode, config); } } }; } else if (isCollPage) { customStyle += `.card-information__button button[disabled] { display: none;}.restore-email-wrapper{width:100%}.product-restore-email,.product-restore-email .email-me-inlineButton{margin:0 !important} .restore-email-wrapper+.item_cart_items { display: none; }.email-me-button.email-me-inlineButton { font-weight: 600 !important; }@media only screen and (max-width: 768px){ .email-me-button.email-me-inlineButton { font-size: 12px !important; } }`; } break; } case 19073267: { customFeature = function () { if (q("#join-mailing-list")) { q("#join-mailing-list").removeAttribute("checked"); } }; break; } case 66072805644: { customFeature = function () { setTimeout(() => { const n = q("#email-me-frame .buyer-phone"); if (n) { n.placeholder = n.placeholder.substring(1); } }, 2000); }; break; } case 66681012522: case 76843450717: case 55393976386: { qa( '.maxus-productdetail__options>.swatch[data-option-index="0"]>.soldout' ).forEach((item) => { item.addEventListener("click", function () { const color = q( '.single-option-selector[data-option="option2"]' ).value; const val = item.querySelector("input").value; const title = val + " / " + color; const variant = variantData.find( (it) => it.title.indexOf(title) !== -1 ); if (variant) { handleVariantChange(variant.id); } }); }); break; } case 48441458840: { if (ShopifyAnalytics.meta.page.pageType === "collection") { if (themeId === 137027780861) { customStyle += `.quick-add__submit.button.button--full-width.button--secondary[disabled]{display:none}`; } customFeature = function () { qa(".product-restore-email").forEach( (i) => (i.parentNode.style.gridRowStart = 3) ); }; break; } } case 73761816874: { collectionAccess = true; } } } })();